#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/time.h>

#define N 128

// 01-socket 192.168.1.2  8000
// argv[0]   arv[1]       argv[2]
int main(int argc, char const *argv[])
{

    int sockfd, ret, newsockfd;
    struct sockaddr_in myaddr, client_addr;
    socklen_t addrlen;
    char buf[N] = {0};

    if (argc != 3)
    {
        fprintf(stderr, "错误:运行程序时请带入参数(./10-tcp_select_server ip port)\n");
        exit(-1);
    }

    // 创建一个socket
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket");
        exit(-1);
    }
    printf("sockfd=%d\n", sockfd);

    memset(&myaddr, 0, sizeof(myaddr));           // 清空结构体
    memset(&client_addr, 0, sizeof(client_addr)); // 清空结构体
    myaddr.sin_family = AF_INET;                  // 什么类型的通信
    myaddr.sin_port = htons(atoi(argv[2]));       // 设置socket 的固定端口
    myaddr.sin_addr.s_addr = inet_addr(argv[1]);  // 把字符串ip转换成网络二进制的ip地址
    ret = bind(sockfd, (struct sockaddr *)&myaddr, sizeof(myaddr));
    if (ret < 0)
    {
        perror("bind");
        exit(-1);
    }
    ret = listen(sockfd, 10);
    if (ret < 0)
    {
        perror("listen");
        exit(-1);
    }
    // system("netstat -ant");
    addrlen = sizeof(client_addr); //这个赋值如果缺少, 可能会引发程序错误

    // 设置一个监听集合(表,数组)
    fd_set rdfs, tmpfs;    // 定义一个 可读的表
    FD_ZERO(&rdfs);        // 清空监控表
    FD_SET(sockfd, &rdfs); // 把监听socket 也加入和监控表内

    int maxfd = sockfd;

    while (1)
    {
        tmpfs = rdfs; // 使用rdfs 初始化 tmpfs

        // 执行这个函数后, 会轮询监控表中的文件状态, 如果有可读的, 就立即返回
        // 如果没有可读的就阻塞 , 直到有文件可读
        // select 函数调用后, 会对tmpfs 进行写操作, tmpfs的值发生变化
        ret = select(maxfd + 1, &tmpfs, NULL, NULL, NULL);
        if (ret < 0)
        {
            perror("select");
            exit(-1);
        }
        // 检查一下哪一个文件 可读
        for (int i = 0; i <= maxfd; i++)
        {
            // 检查i 在监控表中是否可读 , 如果可读 , 返回为真
            // 如果不可读, 返回假
            if (FD_ISSET(i, &tmpfs))
            {
                // 如果i 是sockfd, 表示有客户端发起了tcp的连接(三次握手)
                if (i == sockfd)
                {
                    newsockfd = accept(sockfd, (struct sockaddr *)&client_addr, &addrlen); // 接收客户端tcp连接请求
                    if (newsockfd < 0)
                    {
                        perror("accept");
                        exit(-1);
                    }
                    printf("newsockfd=%d\n", newsockfd);
                    printf("%s & %d is connected\n", inet_ntoa(client_addr.sin_addr),
                           ntohs(client_addr.sin_port));
                    // 把newsockfd 加入到rdfs监控表内
                    FD_SET(newsockfd, &rdfs);

                    // 更新最大文件描述符
                    maxfd = (maxfd > newsockfd) ? maxfd : newsockfd;
                }
                else //如果不是sockfd , 那就是有客户端发送了数据, 这个时候要处理客户端发送的数据
                {
                    ret = recv(i, buf, N, 0);
                    if (ret == 0) // 客户端主动关闭连接, read函数返回值为0
                    {
                        close(i);
                        FD_CLR(i, &rdfs); // 从监控表中移除
                    }
                    else if (ret < 0)
                    {
                        perror("read");
                        exit(-1);
                    }
                    else // ret >0 表示收到了数据
                    {
                        strcpy(buf, "server is reccived!!");
                        ret = send(i, buf, N, 0);
                        if (ret < 0)
                        {
                            perror("send");
                            exit(-1);
                        }
                    }
                }
            }
        }
    }

    close(sockfd);

    return 0;
}